// Copyright 2010 Abiola Ibrahim <abiola89@gmail.com>. All rights reserved.
// Use of this source code is governed by New BSD License
// http://www.opensource.org/licenses/bsd-license.php
// The content and logo is governed by Creative Commons Attribution 3.0
// The mascott is a property of Go governed by Creative Commons Attribution 3.0
// http://creativecommons.org/licenses/by/3.0/

package util

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strings"
)

var (
	comment = "//Generated by gopages%s, do not edit\n//This file will be overwritten during build\n\n"
	method  = `func Render%s(writer http.ResponseWriter, request *http.Request){
					request.ParseForm()
					writer.Header().Set("Content-Type", "text/html")
					print := func(toPrint ...interface{}){
						fmt.Fprint(writer, toPrint...)
					}
					formValue := func(keyToGet string) string{
						return request.FormValue(keyToGet)
					}
					_ = print; _ = formValue// prevent initialization runtime error

			`
	importstring = "\nimport(\n"
	printstring  = "fmt.Fprint(writer, `%s`)\n"

	//directory where generated codes will be stored
	DIR = path.Join(os.Getenv("PWD"), "pages")
)

//Page type
type Page struct {
	imports       []string
	page          string
	content       string
	generatedPage string
	parsed        bool
}

// create a new instance of page
func NewPage(page string) (p *Page, err error) {
	p = new(Page)
	p.page = page
	f, err := os.Open(page)
	f.Close()
	return
}

// parse the page and stores the resulting string in the buffer
// use ParseToFile instead to write to string to file
func (this *Page) Parse() (err error) {
	data, err := ioutil.ReadFile(this.page) //reads file into memory
	if err != nil {
		return
	}
	importsParser := NewQuoteParser(string(data), "{{", "}}")
	imports, p, er := importsParser.Next()
	if er != nil {
		err = er
		return
	}
	content := importsParser.String()
	if len(strings.TrimSpace(p)) > 0 {
		content = p
	}
	this.imports = strings.Fields(imports)
	buffer := NewStringBuilder(fmt.Sprintf("%spackage %s\n", fmt.Sprintf(comment, " from "+this.page), "pages"))
	buffer.Append(importstring)
	for _, im := range this.imports {
		if im != "net/http" && im != "fmt" && im != "code.google.com/p/gopages/pkg" {
			buffer.Append(fmt.Sprintf("\"%s\"\n", im))
		}
	}
	buffer.Append("\t\"net/http\"\n\t\"fmt\"\n\t\"code.google.com/p/gopages/pkg\"\n)\n")
	pg := strings.Split(this.page, "/")
	pg = strings.Split(strings.Join(pg, ""), ".")
	buffer.Append(fmt.Sprintf(method, strings.Join(pg, "")))
	content, err = ParseFragments(content, this.page)
	if err != nil {
		return err
	}
	buffer.Append(content)
	buffer.Append("\n}\n")
	initStr := fmt.Sprintf("\ngopages.ParsedPaths[\"pages/%s.go\"] = \"%s\"\n", strings.Join(pg, ""), this.page)
	buffer.Append(fmt.Sprintf(initfunc, initStr))
	this.content = buffer.Content()
	return err
}

// behaves like Parse but writes to the corresponding output file
func (this *Page) ParseToFile() (err error) {
	if this.parsed {
		return
	}
	err = this.Parse()
	if err != nil {
		return
	}
	tmp := strings.Split(this.page, "/")
	tmp = strings.Split(strings.Join(tmp, ""), ".")
	file := strings.Join(tmp, "")
	this.generatedPage = path.Join(DIR, file+".go")
	err = ioutil.WriteFile(this.generatedPage, []byte(this.content), 0666)
	if err != nil {
		return
	}
	this.parsed = true
	err = Format(this.generatedPage)
	return
}

// formats the source code using gofmt
func Format(source string) (err error) {
	//gofmt := os.Getenv("GOBIN")
	gofmt, _ := exec.LookPath("gofmt")
	if len(gofmt) == 0 {
		return errors.New("gofmt not found in PATH")
	}
	fd := []*os.File{os.Stdin, os.Stdout, os.Stderr}
	file := source
	process, err := os.StartProcess(gofmt, []string{"", "-w", file}, &os.ProcAttr{Env: os.Environ(), Files: fd})
	if err != nil {
		println(err.Error())
		return
	} else {
		process.Wait()
	}
	return nil
}

func ParseCodeString(content, name string) (string, error) {
	codeParser := NewQuoteParser(content, "<?go", "?>")
	codeBuffer := NewStringBuilder("")
	err := codeParser.Parse()
	if err != nil {
		return "", err
	}
	codes, html := codeParser.Parsed(), codeParser.Outer()
	for i, code := range codes {
		if strings.TrimSpace(html[i]) != "" {
			codeBuffer.Append(fmt.Sprintf(printstring, html[i]))
		}
		if strings.TrimSpace(code) == "" && len(codes) == 1 {
			break
		}
		codeBuffer.Append(fmt.Sprintf("%s\n", code))
	}
	return codeBuffer.Content(), nil
}

func ParseFragments(content, name string) (string, error) {
	fragmentsParser := NewQuoteParser(content, "<go:include", ">")
	err := fragmentsParser.Parse()
	if err != nil {
		err = errors.New("error from " + name + " :" + err.Error())
		return "", err
	}
	buffer := NewStringBuilder("")
	frags, others := fragmentsParser.Parsed(), fragmentsParser.Outer()
	for i, frag := range frags {
		code, err := ParseCodeString(others[i], name)
		if err != nil {
			err = errors.New("error from " + name + " :" + err.Error())
			return "", err
		}
		if frag == "" && len(frags) == 1 {
			return code, nil
		}
		buffer.Append(code)
		if frag == "" && len(frags) == i+1 {
			break
		}
		st := strings.Split(frag, "\"")
		if len(st) < 3 {
			return "", errors.New("error from " + name + " around " + frag)
		}
		data, err := ioutil.ReadFile(st[1])
		if err != nil {
			return "", errors.New("error from " + st[1] + " :" + err.Error())
		}
		frag, err = ParseFragments(string(data), st[1])
		if err != nil {
			return "", err
		}
		buffer.Append(fmt.Sprintf("%s\n", frag))
	}
	return buffer.Content(), nil
}

//codes that will be generated
const (
	httphandler = "http.Handle(\"/%s\", http.HandlerFunc(Render%s))\n"
	initfunc    = "func init(){%s}\n"
)

//add handlers to specified pages in settings file
func AddHandlers(pages []string) (err error) {
	buffer := NewStringBuilder(fmt.Sprintf("%spackage %s\n", fmt.Sprintf(comment, ""), "pages"))
	buffer.Append(importstring)
	imports := []string{"code.google.com/p/gopages/pkg"}
	for _, im := range imports {
		buffer.Append(fmt.Sprintf("\"%s\"\n", im))
	}
	buffer.Append("\n)\n")
	functions := NewStringBuilder("")
	pathmap := NewStringBuilder("")
	for i := 0; i < len(pages); i++ {
		tmp := strings.Split(pages[i], "/")
		tmp = strings.Split(strings.Join(tmp, ""), ".")
		f := strings.Join(tmp, "")
		functions.Append(fmt.Sprintf("gopages.ParsedPages[\"%s\"] = Render%s\n", pages[i], f))
		pathmap.Append(fmt.Sprintf("pages/%s.go %s\n", f, pages[i]))
	}
	buffer.Append(fmt.Sprintf(initfunc, functions.Content()))
	file := "pages/handler.go"
	err = ioutil.WriteFile(file, []byte(buffer.Content()), 0666)
	if err != nil {
		return
	}
	err = Format(file)
	if err != nil {
		return
	}
	ioutil.WriteFile(PATHS_FILE, []byte(pathmap.Content()), 0666)
	return
}
